#include "loadImageTexture.h"
#include "png/png.h"
#include "png/pngconf.h"
#include "png/pnglibconf.h"
#include <setjmp.h>
#include <iostream>
#include "ByteBuffer.h"

static void PngReadCallBack(png_structp png_ptr, png_bytep data, png_size_t length)
{
	ByteBuffer* buf = (ByteBuffer*)png_get_io_ptr(png_ptr);
	buf->read(data, length);
}

GLuint LoadPngTexture(const char* path)
{
	ByteBuffer bufData(path);
	if (bufData.size() < 8)
	{
		return 0;
	}
	return LoadPngTexture(bufData);
}

GLuint LoadPngTexture(ByteBuffer& bufData)
{
	if (png_sig_cmp(bufData.data(), 0, 8))
	{
		return 0;
	}

	png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
	png_infop info_ptr = png_create_info_struct(png_ptr);

	if (setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);
		std::cout << "LoadPngTexture() Error during init_io"<< std::endl;
		return 0;
	}
	png_set_read_fn(png_ptr, &bufData, PngReadCallBack);

	png_read_info(png_ptr, info_ptr);

	int width = png_get_image_width(png_ptr, info_ptr);
	int height = png_get_image_height(png_ptr, info_ptr);
	png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
	png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

	// force palette images to be expanded to 24-bit RGB
	// it may include alpha channel
	if (color_type == PNG_COLOR_TYPE_PALETTE)
	{
		png_set_palette_to_rgb(png_ptr);
	}
	// low-bit-depth grayscale images are to be expanded to 8 bits
	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
	{
		bit_depth = 8;
		png_set_expand_gray_1_2_4_to_8(png_ptr);
	}
	// expand any tRNS chunk data into a full alpha channel
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
	{
		png_set_tRNS_to_alpha(png_ptr);
	}
	// reduce images with 16-bit samples to 8 bits
	if (bit_depth == 16)
	{
		png_set_strip_16(png_ptr);
	}

	// Expanded earlier for grayscale, now take care of palette and rgb
	if (bit_depth < 8)
	{
		png_set_packing(png_ptr);
	}
	// update info
	png_read_update_info(png_ptr, info_ptr);
	color_type = png_get_color_type(png_ptr, info_ptr);

	GLenum inFormat = GL_RGBA8, extFormat = GL_RGBA;
	switch (color_type)
	{
	case PNG_COLOR_TYPE_GRAY:
		inFormat = GL_R8;
		extFormat = GL_RED;
		break;
	case PNG_COLOR_TYPE_GRAY_ALPHA:
		inFormat = GL_RG8;
		extFormat = GL_RG;
		break;
	case PNG_COLOR_TYPE_RGB:
		inFormat = GL_RGB8;
		extFormat = GL_RGB;
		break;
	case PNG_COLOR_TYPE_RGB_ALPHA:
		inFormat = GL_RGBA8;
		extFormat = GL_RGBA;
		break;
	default:
		break;
	}

	// read png data
	png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
	ByteBuffer pixelData;
	pixelData.resize(rowbytes * height);


	// 
	//for (unsigned short i = 0; i < height; ++i)
	//{
	//	png_read_row(png_ptr, pixelData.data() + rowbytes * i, nullptr);
	//}

	png_bytep* row_pointers = new png_bytep[height];
	for (unsigned short i = 0; i < height; ++i)
	{
		row_pointers[i] = pixelData.data() + i * rowbytes;
	}
	png_read_image(png_ptr, row_pointers);
	delete[]row_pointers;

	png_read_end(png_ptr, info_ptr);
	png_destroy_read_struct(&png_ptr, (info_ptr) ? &info_ptr : 0, 0);

	GLuint tex = 0;
	glGenTextures(1, &tex);
	if (tex == 0)
	{
		return 0;
	}
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexStorage2D(GL_TEXTURE_2D, 1, inFormat, width, height);
	if (inFormat != GL_RGBA8) 
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	}
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, extFormat, GL_UNSIGNED_BYTE, pixelData.data());

	if (GL_R8 == inFormat)
	{
		GLint swizzle[]{ GL_RED, GL_RED , GL_RED , GL_ONE };
		glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
	}
	else if (GL_RG8 == inFormat)
	{
		GLint swizzle[]{ GL_RED, GL_RED , GL_RED , GL_GREEN };
		glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
	}

	return tex;
}
